package com.hockeo.tinygds;

import java.io.Serializable;

/**
 * Typesafe version of the Google App Engine Key class which is used by the Google
 * Datastore.
 *
 * @param <T> the type of the object which is referenced by this TKey
 * @version $Id$
 * @author jjanke
 */
public class TKey<T> implements Serializable, Comparable<TKey<?>>
{
  private static final long  serialVersionUID = 1L;

  /**
   * This class instance allows to find the object's kind and discriminator by using the
   * {@link TMetaData} class.
   */
  private Class<? extends T> d_entityClass;

  private TKey<?>            d_keyParent;

  private Long               d_lId;

  private String             d_strName;

  /** Argument-less constructor for GWT/RPC. */
  private TKey()
  {}

  /**
   * Creates a new TKey instance not being based on a parent key.
   *
   * @param cls the entity class the key is intended for
   * @param lId the id of the object this key is for
   */
  public TKey( Class<? extends T> cls, long lId )
  {
    this( null, cls, lId );
  }

  /**
   * Creates a new TKey instance not being based on a parent key.
   *
   * @param cls the entity class the key is intended for
   * @param strName the name of the key
   */
  public TKey( Class<? extends T> cls, String strName )
  {
    this( null, cls, strName );
  }

  /**
   * Creates a new TKey instance.
   *
   * @param keyParent the parent key or <code>null</code> if this key has no parent
   * @param cls the entity class the key is intended for
   * @param lId the id of the object this key is for
   */
  public TKey( TKey<?> keyParent, Class<? extends T> cls, long lId )
  {
    d_keyParent = keyParent;
    d_entityClass = cls;
    d_lId = lId;
  }

  /**
   * Creates a new TKey instance.
   *
   * @param keyParent the parent key or <code>null</code> if this key has no parent
   * @param cls the entity class the key is intended for
   * @param strName the name of the key
   */
  public TKey( TKey<?> keyParent, Class<? extends T> cls, String strName )
  {
    d_keyParent = keyParent;
    d_entityClass = cls;
    d_strName = strName;
  }

  /**
   * Returns the {@link Class} object of the persistable entity.
   */
  public Class<? extends T> getEntityClass()
  {
    return d_entityClass;
  }

  /**
   * Returns the key's name. If this method returns <code>null</code>, the
   * {@link #getId()} method returns the id which is used by the object this key refers
   * to.
   */
  public String getName()
  {
    return d_strName;
  }

  /**
   * Returns the object's id or <code>null</code>. If <code>null</code> is returned, the
   * object this key is referring to has no id but a name. It can be retrieved using
   * {@link #getName()}.
   */
  public Long getId()
  {
    return d_lId;
  }

  /**
   * Returns the key's Long or String Id depending on which of the two methods
   * {@link #getName()} or {@link #getId()} does not return <code>null</code>.
   */
  public Object getIdOrName()
  {
    return getName() != null ? getName() : getId();
  }

  /**
   * Returns the parent key or <code>null</code> if there is no parent.
   */
  public TKey<?> getParent()
  {
    return d_keyParent;
  }

  /**
   * Compares a key with this key using its entity class, then its parent and finally its
   * Id or name.
   *
   * @param keyOther the key to which this key should be compared to
   */
  @Override
  public int compareTo( TKey<?> keyOther )
  {
    int nCmp;

    // entity class
    //
    nCmp = d_entityClass.getName().compareTo( keyOther.getEntityClass().getName() );
    if ( nCmp != 0 )
      return nCmp;

    // parent key
    //
    nCmp = compareNullables( d_keyParent, keyOther.getParent() );
    if ( nCmp != 0 )
      return nCmp;

    // try the name (if the result is null, we try with the id)
    //
    nCmp = compareNullables( d_strName, keyOther.getName() );
    if ( nCmp != 0 )
      return nCmp;

    return compareNullables( d_lId, keyOther.getId() );
  }

  @SuppressWarnings( "unchecked" )
  private static int compareNullables( Comparable obj1, Comparable obj2 )
  {
    if ( obj1 == null && obj2 == null )
      return 0;
    if ( obj1 == null && obj2 != null )
      return -1;
    else if ( obj1 != null && obj2 == null )
      return 1;
    else
      return obj1.compareTo( obj2 );
  }

  @Override
  public boolean equals( Object obj )
  {
    if ( obj == null )
      return false;

    if ( !( obj instanceof TKey<?> ) )
      return false;

    return this.compareTo( (TKey<?>) obj ) == 0;
  }

  @Override
  public int hashCode()
  {
    if ( d_strName != null )
      return d_strName.hashCode();
    else
      return d_lId.hashCode();
  }
}
